package net.orange.architecture.db;

import java.util.Collection;
import java.util.Date;

import net.orange.architecture.exception.DatabaseException;

/**
 * The hibernate sql statement formator, which can generator the different hql
 * statement according the system internal data query structure (like Orders,
 * Conditions).
 */
public class HibernateHQLFormator {

	/**
	 * The persistent value object class.
	 */
	private Class clazz;

	/**
	 * Disabled the default constructor.
	 */
	@SuppressWarnings("unused")
	private HibernateHQLFormator() {
		super();
	}

	/**
	 * Constructor with the persistent value object class.
	 * 
	 * @param clazz
	 *            The he persistent value object class
	 */
	public HibernateHQLFormator(Class clazz) {
		this.clazz = clazz;
	}

	/**
	 * Format the order to SQL statement.
	 * 
	 * @param ord
	 *            The order.
	 * @return The relevant SQL Statement.
	 * @throws Exception
	 */
	public String toSQL(Order ord) throws Exception {
		if (ord == null)
			return "";
		else
			return ord.getFieldName() + " " + formatOrder(ord.getOrderBy());
	}

	/**
	 * Format the order collection to SQL statement.
	 * 
	 * @param ords
	 *            The order collection.
	 * @return The relevant SQL Statement.
	 * @throws Exception
	 */
	public String toSQL(Orders ords) throws Exception {
		if (ords == null || ords.isEmpty())
			return "";

		String s = "";

		for (Order ord : ords.toArray())
			s += s.equals("") ? "order by " + toSQL(ord) : ", " + toSQL(ord);

		return s;
	}

	/**
	 * Format the condition to SQL statement.
	 * 
	 * @param cnd
	 *            The condition.
	 * @return The relevant SQL Statement.
	 * @throws Exception
	 */
	public String toSQL(Condition cnd) throws Exception {

		if (cnd != null)
			return formatField(cnd.getFieldName()) + " "
					+ formatOperator(cnd.getOperator()) + " "
					+ formatValue(cnd.getFieldValue(), cnd.getOperator());
		else
			return "";
	}

	/**
	 * Format the condition collection to SQL statement.
	 * 
	 * @param cnds
	 *            The condition collection.
	 * @return The relevant SQL Statement.
	 * @throws Exception
	 */
	public String toSQL(Conditions cnds) throws Exception {
		if (cnds == null || cnds.isEmpty())
			return "";

		String s = "";

		for (ConditionPair cnd : cnds.toArray()) {
			s += s.equals("") ? " where " + toSQL(cnd.getCondition()) : " "
					+ formatRelation(cnd.getRelation()) + " "
					+ toSQL(cnd.getCondition());
		}
		return s;
	}

	/**
	 * Format the item name
	 * 
	 * @param fieldName The item name
	 */
	private String formatField(String field) {
		if (field.indexOf("__") > 0)
			return getEntityName().toLowerCase() + "."
					+ field.replaceFirst("__", ".");
		else
			return getEntityName().toLowerCase() + "." + field;
	}

	/**
	 * Format the order to SQL Statement.
	 * 
	 * @param o
	 *            The oder
	 * @return The relevant SQL Statement.
	 */
	private String formatOrder(int o) {
		if (o == Order.DESC)
			return "desc";
		else
			return "asc";
	}

	/**
	 * Format the operator to SQL Statement.
	 * 
	 * @param o
	 *            The operator.
	 * @return The relevant SQL Statement.
	 * @throws Exception
	 */
	private String formatOperator(Operator o) throws Exception {
		if (o.getOp() == Operator.EQUAL)
			return "=";
		else if (o.getOp() == Operator.NOT_EQUAL)
			return "<>";
		else if (o.getOp() == Operator.LARGER_THAN)
			return ">";
		else if (o.getOp() == Operator.LESS_THAN)
			return "<";
		else if (o.getOp() == Operator.NOT_LARGER_THAN)
			return "<=";
		else if (o.getOp() == Operator.NOT_LESS_THAN)
			return ">=";
		else if (o.getOp() == Operator.LIKE)
			return "like";
		else if (o.getOp() == Operator.STR_IN)
			return "like";
		else if (o.getOp() == Operator.IS_NULL)
			return "is null";
		else if (o.getOp() == Operator.IS_NOT_NULL)
			return "is not null";
		else if (o.getOp() == Operator.IN_NUM)
			return "in";
		else if (o.getOp() == Operator.IN_STR)
			return "in";
		else
			throw new DatabaseException("Unsupport this SQL operator: \""
					+ o.getOp() + "\"");
	}

	/**
	 * Format the value to SQL Statement.
	 * 
	 * @param o
	 *            The value.
	 * @param r
	 *            The operator.
	 * @return The relevant SQL Statement.
	 * @throws Exception
	 */
	private String formatValue(Object o, Operator r) throws Exception {
		if (r.getOp() != Operator.IN_NUM && r.getOp() != Operator.IN_STR)
			return "?";
		else if (o instanceof Collection)
			return "(" + formatInstr((Collection) o) + ")";
		else
			throw new DatabaseException(
					"Invalid value with operator IN_STR or IN_NUM.");
	}

	/**
	 * Format the relation to SQL Statement.
	 * 
	 * @param r
	 *            The relation.
	 * @return The relevant SQL Statement.
	 * @throws Exception
	 */
	private String formatRelation(Relation r) throws Exception {
		if (r.getRl() == Relation.AND)
			return "and";
		else if (r.getRl() == Relation.OR)
			return "or";
		else
			throw new DatabaseException("Unsupport the SQL operator: \""
					+ r.getRl() + "\"");
	}

	/**
	 * Format the "in" collection value string.
	 * 
	 * @param c
	 *            The collection value.
	 */
	private String formatInstr(Collection c) {
		if (c == null || c.size() <= 0)
			return "null";
		String s = "";

		for (Object o : c) {
			if (o instanceof String)
				s += (s.length() <= 0) ? "'" + o + "'" : ",'" + o + "'";
			else if (o instanceof Date)
				s += (s.length() <= 0) ? "'" + o + "'" : ",'" + o + "'";
			else
				s += (s.length() <= 0) ? "" + o + "" : "," + o + "";
		}

		return s;
	}

	/**
	 * Get the entity name.
	 * 
	 * @return The entity name.
	 */
	private String getEntityName() {
		String s0 = clazz.getName();
		String s1 = s0.substring(s0.lastIndexOf(".") + 1, s0.length());
		return s1;
	}
}